﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using ArgusLib;

namespace ArgusLib.Threading
{
	public static class Parallelization
	{
		public delegate void ForEachBody<T>(T item, ThreadState threadState);
		public delegate void ForBody(int index, ThreadState threadState);
		public delegate IEnumerable<TaskBody> TaskBody(ThreadState threadState);

		public delegate void ProgressChangedHandler(double progress);
		public delegate void ProcessTasksProgressHandler(int tasksProcessed, int tasksToProcess);

		public static void ForEach<T>(IEnumerable collection, ForEachBody<T> body, int numberOfThreads, ThreadState threadState, ProgressChangedHandler progressChanged)
		{
			List<Parallelization.TaskBody> tasks = new List<TaskBody>();
			foreach (T item in collection)
			{
				tasks.Add(new Parallelization.TaskBody((tState) =>
					{
						body(item, tState);
						return null;
					}));
			}
			ProcessTasksProgressHandler progress = null;
			if (progressChanged != null)
			{
				progress = new ProcessTasksProgressHandler((finished, total) =>
					 {
						 progressChanged((double)finished / (finished + total));
					 });
			}
			Parallelization.ProcessTasks(tasks, numberOfThreads, threadState, progress);
		}

		public static void ForEach<T>(IEnumerable collection, ForEachBody<T> body, ThreadState threadState, ProgressChangedHandler progressChanged)
		{
			Parallelization.ForEach<T>(collection, body, System.Environment.ProcessorCount, threadState, progressChanged);
		}

		public static void ForEach<T>(IEnumerable collection, ForEachBody<T> body, ThreadState threadState)
		{
			Parallelization.ForEach<T>(collection, body, threadState, null);
		}

		public static void ForEach<T>(IEnumerable collection, ForEachBody<T> body)
		{
			ThreadState threadState = new ThreadState();
			Parallelization.ForEach<T>(collection, body, threadState, null);
		}

		public static void For(int count, ForBody body, int numberOfThreads, ThreadState threadState, ProgressChangedHandler progressChanged)
		{
			TaskBody[] tasks = new TaskBody[count];
			for (int i = 0; i < count; i++)
			{
				int a = i;
				tasks[a] = new Parallelization.TaskBody((tState) =>
				{
					if (tState.CancelRequested == false)
						body(a, tState);
					return null;
				});
			}
			ProcessTasksProgressHandler progress = null;
			if (progressChanged != null)
			{
				progress = new ProcessTasksProgressHandler((finished, total) =>
				{
					progressChanged((double)finished / (finished + total));
				});
			}
			Parallelization.ProcessTasks(tasks, numberOfThreads, threadState, progress);
		}

		public static void For(int count, ForBody body, ThreadState threadState, ProgressChangedHandler progressChanged)
		{
			Parallelization.For(count, body, System.Environment.ProcessorCount, threadState, progressChanged);
		}

		public static void For(int count, ForBody body, ThreadState threadState)
		{
			Parallelization.For(count, body, threadState, null);
		}

		public static void For(int count, ForBody body)
		{
			ThreadState threadState = new ThreadState();
			Parallelization.For(count, body, threadState, null);
		}

		public static void ProcessTasks(IEnumerable<TaskBody> tasks, int numberOfThreads, ThreadState threadState, ProcessTasksProgressHandler progressChanged)
		{
			ThreadStart action = new ThreadStart(()=>
				{
					if (numberOfThreads < 1)
						throw new ArgumentOutOfRangeException("numberOfThreads");

					WaitHandle[] waitHandles = new WaitHandle[numberOfThreads];
					Collections.ISynchronizedStack<TaskBody> taskCollection = new Collections.SynchronizedQueue<TaskBody>(tasks);
					SynchronizedInt32 processed = new SynchronizedInt32(0);

					for (int i = 0; i < numberOfThreads; i++)
					{
						waitHandles[i] = new ManualResetEvent(true);
					}

					Thread[] threads = new Thread[numberOfThreads];
					
					for (int i = 0; i < numberOfThreads; i++)
					{
						ManualResetEvent waitHandle = (ManualResetEvent)waitHandles[i];
						threads[i] = new Thread(new ThreadStart(() =>
						{
							while (threadState.CancelRequested == false)
							{
								if (taskCollection.Count > 0 && threadState.CancelRequested == false)
									waitHandle.Reset();

								while (taskCollection.Count > 0 && threadState.CancelRequested == false)
								{
									threadState.WaitForResumption();

									TaskBody task;
									if (taskCollection.TryPop(out task) == false)
										break;
									IEnumerable<TaskBody> newTasks = task(threadState);

									if (newTasks != null)
										taskCollection.PushRange(newTasks);

									if (progressChanged != null)
									{
										processed.Increment();
										progressChanged(processed.Object, taskCollection.Count);
									}
								}
								waitHandle.Set();

								if (WaitHandle.WaitAll(waitHandles, 50) == true)
									break;
							}
							waitHandle.Set();
						}));

						if (taskCollection.Count > 0 && threadState.CancelRequested == false)
						{
							waitHandle.Reset();
							threads[i].Start();
						}
					}

					WaitHandle.WaitAll(waitHandles);
				});

			if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
			{
				Thread thread = new Thread(action);
				thread.Start();
				thread.Join();
			}
			else
			{
				action();
			}
		}

		public static void ProcessTasks(IEnumerable<TaskBody> tasks, ThreadState threadState, ProcessTasksProgressHandler progressChanged)
		{
			Parallelization.ProcessTasks(tasks, System.Environment.ProcessorCount, threadState, progressChanged);
		}

		public static void ProcessTasks(IEnumerable<TaskBody> tasks, ThreadState threadState)
		{
			Parallelization.ProcessTasks(tasks, System.Environment.ProcessorCount, threadState, null);
		}

		public static void ProcessTasks(IEnumerable<TaskBody> tasks)
		{
			ThreadState threadState = new ThreadState();
			Parallelization.ProcessTasks(tasks, System.Environment.ProcessorCount, threadState, null);
		}
	}
}
